#include <stdio.h>
#include <unistd.h>
#include <stdbool.h>
#include <string.h>
#include <sys/epoll.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <netinet/in.h>

// 使用 ET 模式，但是每次只读一个字节
// 结论是虽然采用 ET 模式，但是读取出来的数据还是有顺序的

static int createAndBind(int port)
{
	int fd = socket(PF_INET, SOCK_STREAM, 0);

	struct sockaddr_in serveraddr;
	serveraddr.sin_family = AF_INET;
	serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
	serveraddr.sin_port = htons(port);

	bind(fd, (struct sockaddr*)&serveraddr, sizeof(struct sockaddr_in));
	return fd;
}

static int makeNonBlocking(int sfd)
{
	int flags = fcntl(sfd, F_GETFL, 0);
	flags |= O_NONBLOCK;
	fcntl(sfd, F_SETFL, flags);

	return 0;
}

int main(void)
{
	int listenFd = createAndBind(9999);
	makeNonBlocking(listenFd);
	listen(listenFd, SOMAXCONN);

	// Since Linux 2.6.8, the size argument is ignored, but must be greater than zero;
	int epollFd = epoll_create(1);

	struct epoll_event ev;
	ev.data.fd = listenFd;
	ev.events = EPOLLIN;      // 这里不用 ET
	if (epoll_ctl(epollFd, EPOLL_CTL_ADD, ev.data.fd, &ev) == -1)
		perror("epoll ctl return -1");

	const int eventNumber = 10;
	struct epoll_event events[eventNumber];
	while(true)
	{
		int ret = epoll_wait(epollFd, events, eventNumber, -1);
		printf("epoll ret is %d\n", ret);
		if (ret <= 0)
		{
			// 为了简单忽略其他问题
			continue;
		}

		for (int i = 0; i < ret; i++)
		{
			int e = events[i].events;
			int fd = events[i].data.fd;
			if (fd == listenFd)
			{
				// 将新的链接加入到 epoll 中
				struct sockaddr_in cltAddr;
				socklen_t addrLen = sizeof(struct sockaddr_storage);
				int newFd = accept(listenFd, (struct sockaddr*)&cltAddr, &addrLen);
				printf("accept new connection, fd is %d\n", newFd);

				struct epoll_event ev;
				ev.data.fd = newFd;
				ev.events = EPOLLIN | EPOLLET;   // 边缘触发
				epoll_ctl(epollFd, EPOLL_CTL_ADD, ev.data.fd, &ev);
			}
			else if (e & EPOLLIN)
			{
				// 故意每次只读一个字节
				char buffer[1024];
				ssize_t readSize = read(fd, buffer, 1);
				buffer[readSize] = 0;
				printf("read [%s] from fd: %d, size is %zd\n", buffer, fd, readSize);
			}
			else if (e & (EPOLLHUP | EPOLLERR))
			{
				// if all fds are cloesd, should break
				printf("will close fd(%d), event is %d\n", fd, e);
				close(fd);
			}
		}
	}

	return 0;
}